/*
 *
 * Programma per la gestione di sequenze. GSeq permette di inserire,
 * cercare ed eliminare elementi in una sequenza. La sequenza può
 * anche essere letta o salvata da/su file.
 *
 * Gli elementi della sequenza su cui lavora il programma possono
 * avere chiave di tipo numerico oppure stringa (vedere ::tipo_chiave_t)
 *
 * La sintassi per lanciare il programma da riga di comando e' la seguente:
 *
 * GSeq [-n|-s]
 *
 * -n: chiave di tipo numerico; -s: chiave di tipo stringa
 *
 * Vedere la documentazione della funzione ::main
 * per maggiori dettagli sulle funzionalita' e sulla loro
 * implementazione.
 *
 * @author Marko Bertogna
 */

/* 
 * File contenente il modulo Main.
 */

#include <iostream>
#include <fstream>
#include <cstring>
#include <cstdlib>
#include <sstream>
#include "strutt_dati.h"
#include "manip_stampa.h"
#include "fileIO.h"
#include <gtk/gtk.h>

using namespace std ;

#ifdef DEBUG_MODE
/* Maschera per il tracing/debugging */
unsigned int MASK = 1 ;
#endif

static GtkBuilder *builder;
static desc_sequenza sequenza ;

extern "C" gboolean handler_delete_event(GtkWidget *widget,
					 GdkEvent  *event,
					 gpointer   user_data)  
{
    /*
     * mediante compilazione condizionale facciamo compilare prima la
     * versione con la stampa, poi quella con l'uscita dal main loop
     * (per far compilare la seconda versione basta scrivere false al posto
     * di true nella seguente direttiva)
     */
#if false
    cout<<"Handler delete_event"<<endl ;
#else
    gtk_main_quit() ;
#endif
    return TRUE ;
}

/*
extern "C" void add_activate_handler (GtkMenuItem *menuitem, gpointer user_data)
{
	GtkWidget *ins_dialog = GTK_WIDGET(gtk_builder_get_object(builder,"dialog1"));
	gtk_widget_show_all(ins_dialog);
}
*/ 

// Occorre passare la finestra di dialogo come user data all'handler
extern "C" void show_dialog_handler (GtkMenuItem *menuitem, gpointer dialog)
  {
  	gtk_widget_show_all(GTK_WIDGET(dialog));
  }

extern "C" void hide_main_window(GtkButton *button, gpointer user_data)
{
	gtk_widget_hide(gtk_widget_get_toplevel(GTK_WIDGET(button)));
}

// questa funzione è dichiarata static perché è utilizzata solo
// internamente a questo file
static void mostra_error_alert(const char *str_princ, const char *str_secon)
{
    GtkLabel *label_principale = 
	GTK_LABEL(gtk_builder_get_object(builder, "label_principale")) ;
    char markup_str[MAXLUN] = "<span weight=\"bold\" size=\"larger\">" ;
    strcat(markup_str, str_princ) ;
    strcat(markup_str, "</span>") ;
    gtk_label_set_markup(label_principale, markup_str);

    GtkLabel *label_secondaria = 
	GTK_LABEL(gtk_builder_get_object(builder, "label_secondaria")) ;
    gtk_label_set_text(label_secondaria, str_secon);

    GtkWidget *error_alert = 
	GTK_WIDGET(gtk_builder_get_object(builder, "error_alert")) ;
    gtk_widget_show_all(error_alert) ;
}

/*  Aggiornamento contenuto della text view che contiene la sequenza e della
 *  barra di stato; questa funzione è dichiarata static perché è utilizzata solo
 *  internamente a questo file
 */
static void aggiorna_text_view_sequenza()
{
    GtkTextView *textview_sequenza =
	GTK_TEXT_VIEW(gtk_builder_get_object(builder, "textview1")) ;
    GtkTextBuffer *buffer = 
	gtk_text_view_get_buffer(textview_sequenza) ;

    ostringstream oss ;
    for (int i = 0 ; i < sequenza.num_elem ; i++)
	stampa_elem(*sequenza.elementi[i], oss);

    gtk_text_buffer_set_text(buffer, oss.str().c_str(), -1) ;

    GtkLabel *label_numelem = 
	GTK_LABEL(gtk_builder_get_object(builder, "label_numelem")) ;

    oss.str("") ; oss.clear() ;
    oss<<sequenza.num_elem<<" elementi (array da "
       <<sequenza.dim<<")" ;

    gtk_label_set_text(label_numelem, oss.str().c_str());

    GtkLabel *label_ordinata = 
	GTK_LABEL(gtk_builder_get_object(builder, "label_ordinata")) ;

    gtk_label_set_text(label_ordinata, sequenza.ordinata ? "ordinati" : 
		       "non ordinati") ;

    GtkLabel *label_tipochiave = 
	GTK_LABEL(gtk_builder_get_object(builder, "label_tipochiave")) ;

    gtk_label_set_text(label_tipochiave,
		       sequenza.tipo_chiave == CHIAVE_NUMERICA ? 
		       "chiave numerica" : 
		       "chiave stringa");
}

extern "C" void load_handler( GtkWidget *widget,
			       gpointer   data )
{
    carica(sequenza, "sequenza.txt") ;
    aggiorna_text_view_sequenza() ;
}

extern "C" void save_handler( GtkWidget *widget,
			       gpointer   data )
{
    salva(sequenza, "sequenza.txt") ;
}

extern "C" void remove_handler( GtkWidget *widget,
			      gpointer   data )
{
    GtkEntry *rem_entry =
	GTK_ENTRY(gtk_builder_get_object(builder, "remove_entry")) ;

    const char *chiave = gtk_entry_get_text(rem_entry);
    if (!elimina(sequenza, chiave))
	mostra_error_alert("Elemento non trovato", 
			   "Ricerca terminata") ;
    else {
	aggiorna_text_view_sequenza() ;
	gtk_widget_hide(gtk_widget_get_toplevel(GTK_WIDGET(rem_entry))) ;
    }
}

extern "C" void delete_handler( GtkWidget *widget,
				   gpointer   data )
{
    tipo_chiave_t t = sequenza.tipo_chiave ;
    distruggi(sequenza) ;
    inizializza(sequenza, t) ;
    aggiorna_text_view_sequenza() ;
}

// mostra la documentazione invocando firefox
// notare l'uso del terminatore &, per far tornare il controllo al programma
extern "C" void doc_handler( GtkWidget *widget,
					gpointer   data )
{
    system("firefox html/index.html &") ;
}

/* Disegno di un rettangolo in coordinate cartesiane.
 * L'origine delle coordinate è nell'angolo in basso a sinistra del
 * drawable, mentre l'origine per le funzioni cairo è posto nell'angolo
 * in alto a sinistra. Inoltre le ascisse crescono in verso opposto
 * nei due sistemi di riferimento. Pertanto questa funzione invoca la
 * funzione di libreria cairo per il disegno di un rettangolo passandole
 * le coordinate opportunamente ricalcolate.
 */
static void disegna_rettangolo(GtkWidget *widget, cairo_t *cr, double x, 
				double y, double larghezza, double altezza)
{
    int max_altezza = gtk_widget_get_allocated_height(widget) ;
    cairo_rectangle(cr, x, max_altezza - 1 - y - altezza, larghezza, altezza) ;
}

/*
 * Handler del segnale draw emesso quando l'area deve essere (ri)disegnata.
 */
extern "C" gboolean 
draw_handler( GtkWidget *widget, cairo_t *cr, gpointer data )
{
    if (sequenza.num_elem == 0)
	return TRUE ; // non c'è da disegnare nessun rettangolo, possiamo
                      // uscire

    // segue il calcolo della larghezza e dell'altezza dei rettangoli
    guint larghezza_barre = gtk_widget_get_allocated_width(widget) / 
    							sequenza.num_elem ;
    larghezza_barre = larghezza_barre > 2 ? larghezza_barre - 2 : 1 ;
    
    guint altezza_max_barre = 
	static_cast<gint>(0.9 * gtk_widget_get_allocated_height(widget)) ;

    //Calcolo del valore massimo
    int max_val = atoi(sequenza.elementi[0]->chiave) ;
    for (int i = 1 ; i < sequenza.num_elem ; i++)
	max_val = max_val > atoi(sequenza.elementi[i]->chiave) ?
	           max_val : atoi(sequenza.elementi[i]->chiave);

    // disegno dei rettangoli nel cairo context (altezza normalizzata 
    // rispetto al valore massimo)
    for (int i = 0 ; i < sequenza.num_elem ; i++)
        disegna_rettangolo(widget, cr, i * (larghezza_barre + 2) + 1, 0, 
        	larghezza_barre, 
		altezza_max_barre * (atoi(sequenza.elementi[i]->chiave) / 
		    static_cast<double>(max_val)));
    
    cairo_fill (cr);

    return FALSE;
}
    
extern "C" void insert_handler(GtkButton *button, gpointer user_data)
{
    ins_modo modo ;  //definito attraverso enum in strutt_dati.h

    if(gtk_toggle_button_get_active(
	 GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "radiobutton1"))))
	modo = in_testa ;
    else if(gtk_toggle_button_get_active(
	 GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "radiobutton2"))))
	modo = in_coda ; 
    else if(gtk_toggle_button_get_active(
	 GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "radiobutton3")))) {
	if (sequenza.ordinata)
	    modo = in_ordine;
	else {
	    mostra_error_alert("Inserimento in ordine fallito", 
			       "La sequenza non e' ordinata") ;
	    return ;
	}
    }

    GtkTextView *ins_textview =
	GTK_TEXT_VIEW(gtk_builder_get_object(builder, "textview2")) ;

	    // --------- INIZIO FUNZIONI PER LA LETTURA DEL BUFFER --------------------

    // ottengo il buffer
    GtkTextBuffer *buffer = gtk_text_view_get_buffer(ins_textview) ;

    // definisco i due iteratori
    GtkTextIter start, end;

    // faccio puntare il primo iteratore all'inizio del buffer
    gtk_text_buffer_get_iter_at_offset(buffer, &start, 0);

    // assegno temporaneamente all'altro iteratore il valore del primo
    // per posizionare anche lui all'inizio del buffer
    end = start ;

    // sposto l'altro iteratore alla fine del buffer
    gtk_text_iter_forward_to_end(&end) ;

//	Alternativa:
//	start = gtk_text_buffer_get_start_iter(buffer,&start);
//	end = gtk_text_buffer_get_end_iter(buffer,&end);

//	Oppure, ancora piu' semplice:	
//	gtk_text_buffer_get_bounds(buffer,&start,&end);

    // utilizzando i due iteratori che puntano all'inizio ed alla fine,
    // leggo tutto il contenuto del buffer
    const char *elem_string = 
	gtk_text_buffer_get_text(buffer, &start, &end, TRUE) ;

    // --------- FINE FUNZIONI PER LA LETTURA DEL BUFFER ---------------------

    // metto la string in un istringstream
    istringstream s(elem_string) ;

    // passo l'istringstream alla funzione crea_elem che prende in
    // ingresso un generico istream
    elem *el = crea_elem(s) ;
    if (el == 0) {
	// incaso di errori mostro la finestra di alert ed esco
	mostra_error_alert("Creazione nuovo elemento fallita", 
			   "") ;
	goto exit ;
    }
    inserisci(sequenza, el, modo) ;
    // affinché si veda il nuovo contenuto della sequenza devo
    // aggiornare il contenuto della text view che la mostra sulla
    // finestra principale
    aggiorna_text_view_sequenza() ;

 exit:
    GtkWidget *ins_dialog = GTK_WIDGET(gtk_builder_get_object(builder, "dialog1")) ;
    gtk_widget_hide(ins_dialog) ;
}


/*
 *
 * Legge le opzioni da riga di comando, inizializza la sequenza ed
 * entra in modalita' interattiva, in cui propone all'utente un
 * interfaccia grafica, che permette di accedere alle
 * funzionalita' disponibili. Tali funzionalita' sono: 
 * - inserimento, implementata mediante la funzione ::inserisci 
 * - ricerca, implementata mediante la funzione ::cerca 
 * - eliminazione, implementata mediante la funzione ::elimina 
 * - salvataggio su file, implementata mediante la funzione ::salva 
 * - caricamento da file, implementata mediante la funzione ::carica
 */
int main(int argc, char *argv[])
{      
    if (argc == 1) {
	cout<<"Bisogna selezionare il tipo di chiave!"<<endl ;
	cout<<"Sintassi: GSeq [-n|-s]"<<endl ;
	cout<<"\t-n\tchiave di tipo numerico"<<endl ;
	cout<<"\t-s\tchiave di tipo stringa"<<endl ;
	return 1 ;
    }

    const char optstring[] = "ns" ;
    int c ;
    tipo_chiave_t tipo_chiave ;

    while((c = getopt(argc, argv, optstring) ) != -1) {
	switch(c) {
	case 'n':
	    tipo_chiave = CHIAVE_NUMERICA ;
	    break ;
	case 's':
	    tipo_chiave = CHIAVE_STRINGA ;
	    break ;
	}
    }

    inizializza(sequenza, tipo_chiave) ;

    gtk_init(&argc, &argv);

    /* crea un nuovo oggetto GtkBuilder */
    builder = gtk_builder_new();
    
	/* carichiamo l'interfaccia */
    gtk_builder_add_from_file(builder,"inter_comp.glade", NULL);

    /* 
     * connessione di tutti i segnali,
     */
    gtk_builder_connect_signals(builder, NULL);

    /* partenza del ciclo di gestione degli eventi */
    gtk_main();

    return 0 ;
}
